﻿using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Validation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

namespace CMS6Tools.Management
{
    public partial class CodeChangeTrackForm : DockContent
    {
        #region Properties
        private TfsTeamProjectCollection AuthenticatedTfsTeamProjectCollection;
        #endregion

        #region Ctor
        public CodeChangeTrackForm()
        {
            InitializeComponent();
        }
        #endregion

        #region Form Load
        private void CodeChangeTrackForm_Load(object sender, EventArgs e)
        {
            AuthenticatedTfsTeamProjectCollection = Util.AuthenticatedTfsTeamProjectCollection;
            SourceCodeFolderTextBox.Text = ConfigurationManager.AppSettings["codeChangeTfsDefaultSourceCodeFolder"] ?? "$/CMS 6/CMS 6/src/CMS6_Branch_DEV";
            ExcludedFileExtensionsTextBox.Text = ConfigurationManager.AppSettings["codeChangeExcludedFileExtensions"] ?? ".csproj,.dll,.sln,.vspscc";

            StartDateTimePicker.CustomFormat = "MM/dd/yyyy HH:mm:ss";
            StartDateTimePicker.Value = DateTime.Now.AddDays(-7);
            EndDateTimePicker.CustomFormat = "MM/dd/yyyy HH:mm:ss";
        }
        #endregion

        #region Find
        private void FindButton_Click(object sender, EventArgs e)
        {
            if (AuthenticatedTfsTeamProjectCollection == null || !AuthenticatedTfsTeamProjectCollection.HasAuthenticated)
            {
                MessageBox.Show("Please connect the team project collection at first.");
                return;
            }

            var versionControlService = AuthenticatedTfsTeamProjectCollection.GetService<VersionControlServer>();

            var queryParameters = new QueryHistoryParameters(SourceCodeFolderTextBox.Text, RecursionType.Full);

            if (ByDateRadioButton.Checked)
            {
                var versionStart = new DateVersionSpec(StartDateTimePicker.Value);
                queryParameters.VersionStart = versionStart;

                var versionEnd = new DateVersionSpec(EndDateTimePicker.Value);
                queryParameters.VersionEnd = versionEnd;
            }

            if (ByNumberRadioButton.Checked)
            {
                var startChangesetID = 0;
                int.TryParse(StartChangesetNumberTextBox.Text, out startChangesetID);
                if (startChangesetID > 0)
                    queryParameters.VersionStart = new ChangesetVersionSpec(startChangesetID);

                int endChangesetID = 0;
                int.TryParse(EndChangesetNumberTextBox.Text, out endChangesetID);
                if (endChangesetID > 0)
                    queryParameters.VersionEnd = new ChangesetVersionSpec(endChangesetID);

            }

            var changesets = versionControlService.QueryHistory(queryParameters).ToList();
            ChangesetListBox.Items.Clear();
            foreach (var changeset in changesets)
            {
                ChangesetListBox.Items.Add(string.Format("{0} - {1} - {2} - {3}", changeset.ChangesetId, changeset.CreationDate, changeset.CommitterDisplayName, changeset.Comment));
            }

            if (changesets.Any())
            {
                HistoryTotalLabel.Text = string.Format("Found {0} records", changesets.Count());
            }
            else
            {
                HistoryTotalLabel.Text = "Not found history.";
            }
        }
        #endregion

        #region Generate
        private void GenerateButton_Click(object sender, EventArgs e)
        {
            if (AuthenticatedTfsTeamProjectCollection == null || !AuthenticatedTfsTeamProjectCollection.HasAuthenticated)
            {
                MessageBox.Show("Please connect the team project collection at first.");
                return;
            }

            var excludedFileExtensions = ExcludedFileExtensionsTextBox.Text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var sourceCodeFolder = SourceCodeFolderTextBox.Text;

            var versionControlService = AuthenticatedTfsTeamProjectCollection.GetService<VersionControlServer>();

            var queryParameters = new QueryHistoryParameters(sourceCodeFolder, RecursionType.Full);

            if (ByDateRadioButton.Checked)
            {
                var versionStart = new DateVersionSpec(StartDateTimePicker.Value);
                queryParameters.VersionStart = versionStart;

                var versionEnd = new DateVersionSpec(EndDateTimePicker.Value);
                queryParameters.VersionEnd = versionEnd;
            }

            if (ByNumberRadioButton.Checked)
            {
                var startChangesetID = 0;
                int.TryParse(StartChangesetNumberTextBox.Text, out startChangesetID);
                if (startChangesetID > 0)
                    queryParameters.VersionStart = new ChangesetVersionSpec(startChangesetID);

                int endChangesetID = 0;
                int.TryParse(EndChangesetNumberTextBox.Text, out endChangesetID);
                if (endChangesetID > 0)
                    queryParameters.VersionEnd = new ChangesetVersionSpec(endChangesetID);

            }

            var changesets = versionControlService.QueryHistory(queryParameters).ToList().OrderBy(c => c.ChangesetId).ToList();

            if (changesets.Any())
            {
                var allChangesetsWithChanges = new List<Changeset>();
                var totalCount = changesets.Count;
                var pageSize = 40;
                var maxChangesetNumber = changesets.Last().ChangesetId;
                var minChangesetNumebr = changesets.First().ChangesetId;

                var currentChangesetNumber = minChangesetNumebr;
                while (allChangesetsWithChanges.Count < totalCount)
                {
                    var loopQueryParameter = new QueryHistoryParameters(sourceCodeFolder, RecursionType.Full);
                    loopQueryParameter.IncludeChanges = true;
                    loopQueryParameter.VersionStart = new ChangesetVersionSpec(currentChangesetNumber);

                    var endChangesetNumber = currentChangesetNumber + pageSize;
                    while (!changesets.Any(c => c.ChangesetId == endChangesetNumber))
                        endChangesetNumber--;
                    loopQueryParameter.VersionEnd = new ChangesetVersionSpec(endChangesetNumber);

                    if (endChangesetNumber > currentChangesetNumber)
                        allChangesetsWithChanges.AddRange(versionControlService.QueryHistory(loopQueryParameter).ToList());

                    currentChangesetNumber += pageSize;
                }
                 
                var length = allChangesetsWithChanges.Count;
                for (var i = 0; i < length; i++)
                {
                    var changes = allChangesetsWithChanges[i].Changes
                        .Select(c => new
                        {
                            Path = c.Item.ServerItem.Replace(sourceCodeFolder, string.Empty).Trim('/'),
                            Change = c
                        });

                    var vmcChanges = changes.Where(c => c.Path.StartsWith("VMC/", StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Change).ToList();
                    var visionSearchChanges = changes.Where(c => c.Path.StartsWith("Search/", StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Change).ToList();
                    var cms6Changes = allChangesetsWithChanges[i].Changes.Where(c => !vmcChanges.Contains(c) && !visionSearchChanges.Contains(c)).ToList();

                    if (vmcChanges.Any())
                    {
                    }
                }

                allChangesetsWithChanges = allChangesetsWithChanges.OrderBy(c => c.ChangesetId).ToList();

                var order = 1;
                var regex = new Regex(@"\[([^\[\]]+)\]");
                var allCodeChangeTrackModels = new List<CodeChangesetTrackModel>();
                for (var i = 0; i < allChangesetsWithChanges.Count; i++)
                {
                    var currentChangeset = allChangesetsWithChanges[i];
                    var comment = currentChangeset.Comment ?? string.Empty;
                    var summary = regex.Replace(comment, "");
                    var changesetType = CodeChangesetTrackModel.ChangesetType_Enhancement;
                    var matches = regex.Matches(comment);
                    if (matches.Count >= 3)
                    {
                        var capturedValue = matches[2].Groups[1].Value;
                        if ("BUG".Equals(capturedValue, StringComparison.InvariantCultureIgnoreCase) || capturedValue.IndexOf("BTNET", StringComparison.InvariantCultureIgnoreCase) >= 0)
                            changesetType = CodeChangesetTrackModel.ChangesetType_Bug;
                        else if ("FEATURE".Equals(capturedValue, StringComparison.InvariantCultureIgnoreCase))
                            changesetType = CodeChangesetTrackModel.ChangesetType_Bug;
                        else
                            changesetType = CodeChangesetTrackModel.ChangesetType_Enhancement;
                    }

                    // Changes
                    var needTrackChanges = currentChangeset.Changes.Where(c =>
                    {
                        var extension = System.IO.Path.GetExtension(c.Item.ServerItem);
                        return c.Item.ItemType == ItemType.File && !string.IsNullOrEmpty(extension) && !excludedFileExtensions.Any(d => d.Equals(extension, StringComparison.InvariantCultureIgnoreCase));
                    }).ToList();
                    var changeAdapters = needTrackChanges.Select(c => new
                    {
                        Path = c.Item.ServerItem.Replace(sourceCodeFolder, string.Empty).Trim('/'),
                        Change = c
                    });
                    var vmcChanges = changeAdapters.Where(c => c.Path.StartsWith("VMC/", StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Change).ToList();
                    var visionSearchChanges = changeAdapters.Where(c => c.Path.StartsWith("Search/", StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Change).ToList();
                    var cms6Changes = needTrackChanges.Where(c => !vmcChanges.Contains(c) && !visionSearchChanges.Contains(c)).ToList();

                    var changesTupleArray = new List<Tuple<string, IEnumerable<Change>>>();
                    if (vmcChanges.Any())
                    {
                        changesTupleArray.Add(new Tuple<string, IEnumerable<Change>>("VMC", vmcChanges));
                    }
                    if (visionSearchChanges.Any())
                    {
                        changesTupleArray.Add(new Tuple<string, IEnumerable<Change>>("VisionSearch", visionSearchChanges));
                    }
                    if (cms6Changes.Any())
                    {
                        changesTupleArray.Add(new Tuple<string, IEnumerable<Change>>("CMS6", cms6Changes));
                    }
                     

                    foreach (var changesTuple in changesTupleArray)
                    {
                        var trackModel = new CodeChangesetTrackModel()
                        {
                            Order = order++,
                            ChangesetNumber = currentChangeset.ChangesetId,
                            Summary = summary
                        };

                        // Comment format: [CMS6 DEV][Sprint 63]{[BUG]|[ENHANCE]|[FEATURE]...
                        // Project Type
                        trackModel.ProjectType = changesTuple.Item1;

                        // ChangesetType
                        trackModel.ChangesetType = changesetType;

                        var allChangesetFileItems = new List<ChangesetFileItem>();
                        foreach (var change in changesTuple.Item2)
                        {
                            var changedFile = new ChangesetFileItem();
                            if ((int)(change.ChangeType & ChangeType.Add) == (int)ChangeType.Add)
                            {
                                changedFile.ChangeType = ChangesetFileItem.ChangeType_Added;
                            }
                            else if ((int)(change.ChangeType & ChangeType.Delete) == (int)ChangeType.Delete)
                            {
                                changedFile.ChangeType = ChangesetFileItem.ChangeType_Deleted;
                            }
                            else if ((int)(change.ChangeType & ChangeType.Rename) == (int)ChangeType.Rename) // Rename will add two changes, one is Delete|SourceRename, the other is Rename.
                            {
                                changedFile.ChangeType = ChangesetFileItem.ChangeType_Added;
                            }
                            else
                            {
                                changedFile.ChangeType = ChangesetFileItem.ChangeType_Modified;
                            }
                            changedFile.FileName = change.Item.ServerItem.Replace(sourceCodeFolder, string.Empty).TrimStart('/').ToString();
                            allChangesetFileItems.Add(changedFile);
                        }
                        trackModel.ChangedFiles = allChangesetFileItems;

                        allCodeChangeTrackModels.Add(trackModel);
                    }
                }

                // generate file
                var tempFileName = string.Format("Change Logs_{0}.xlsx", DateTime.Now.ToString("yyyyMMddHHmmss"));
                var exeDirectory = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
                var tempFilePath = System.IO.Path.Combine(exeDirectory, tempFileName);
                var templatePath = System.IO.Path.Combine(exeDirectory, @"Data\CodeChangeTrackTemplate.xlsx");
                var changeLogsSheetName ="ChangeLogs";
                using (var excel = new OpenXmlExcel(tempFilePath, templatePath))
                {
                    excel.Execute("References", ws =>
                    {
                        var sheetData = ws.GetFirstChild<SheetData>();
                        sheetData.Elements<Row>().ToList().ForEach(r =>
                        {
                            var styleIndex = r.RowIndex == 1 ? 1U : 0U;
                            r.Elements<Cell>().ToList().ForEach(c => c.StyleIndex = styleIndex);
                        });
                        ws.Save();
                    });

                    var row = 1;
                    for (var i = 0; i < allCodeChangeTrackModels.Count; i++)
                    {
                        row ++;

                        var startRow = row;
                        var endRow = row;

                        var trackModel = allCodeChangeTrackModels[i];
                        excel.UpdateValue(changeLogsSheetName, "A" + row.ToString(), trackModel.Order.ToString(), valueType: CellValues.Number);
                        excel.UpdateValue(changeLogsSheetName, "B" + row.ToString(), trackModel.ChangesetNumber.ToString(), valueType: CellValues.Number);
                        excel.UpdateValue(changeLogsSheetName, "C" + row.ToString(), trackModel.Summary);
                        excel.UpdateValue(changeLogsSheetName, "D" + row.ToString(), trackModel.ProjectType);
                        excel.UpdateValue(changeLogsSheetName, "E" + row.ToString(), trackModel.ChangesetType);

                        var index = 0;
                        foreach (var changedFile in trackModel.ChangedFiles)
                        {
                            if (index++ > 0)
                                row++;
                            excel.UpdateValue(changeLogsSheetName, "F" + row.ToString(), changedFile.FileName);
                            var formula = string.Format("IF(ISERROR(MATCH(LOWER(CONCATENATE(\".\", REPLACE(RIGHT(TRIM(F{0}),7),1,SEARCH(\".\",RIGHT(TRIM(F{0}),7)),\"\"))),RELEASABLE_EXT,0)), \"NO\", \"YES\")", row);
                            excel.UpdateValueWithFormula("ChangeLogs", "G" + row.ToString(), formula);

                            uint changedFileStyle = 0U;
                            if (changedFile.ChangeType.Equals(ChangesetFileItem.ChangeType_Deleted))
                            {
                                changedFileStyle = 2U;
                            }
                            else if (changedFile.ChangeType.Equals(ChangesetFileItem.ChangeType_Added))
                            {
                                changedFileStyle = 3U;
                            }
                            else
                            {
                                changedFileStyle = 0U;
                            }
                            excel.UpdateValue(changeLogsSheetName, "H" + row.ToString(), changedFile.ChangeType,changedFileStyle);
                        }
                        endRow = row;

                        // Do Merge
                        if (startRow < endRow)
                        {
                            excel.MergeCells(changeLogsSheetName, "A" + startRow.ToString(), "A" + endRow.ToString());
                            excel.MergeCells(changeLogsSheetName, "B" + startRow.ToString(), "B" + endRow.ToString());
                            excel.MergeCells(changeLogsSheetName, "C" + startRow.ToString(), "C" + endRow.ToString());
                            excel.MergeCells(changeLogsSheetName, "D" + startRow.ToString(), "D" + endRow.ToString());
                            excel.MergeCells(changeLogsSheetName, "E" + startRow.ToString(), "E" + endRow.ToString());
                        }
                    }

                    var dataValidations1 = new DataValidations();

                    var changeTypeDataValidation = new DataValidation()
                    {
                        Type = DataValidationValues.List,
                        AllowBlank = true,
                        ShowInputMessage = true,
                        ShowErrorMessage = true,
                        SequenceOfReferences = new DocumentFormat.OpenXml.ListValue<DocumentFormat.OpenXml.StringValue>()
                        {
                            InnerText = string.Format("H2:H{0}", row)
                        },
                        Formula1 = new Formula1("CHANGE_TYPES")
                    };
                    dataValidations1.Append(changeTypeDataValidation);

                    var changesetTypeDataValidation = new DataValidation()
                    {
                        Type = DataValidationValues.List,
                        AllowBlank = true,
                        ShowInputMessage = true,
                        ShowErrorMessage = true,
                        SequenceOfReferences = new DocumentFormat.OpenXml.ListValue<DocumentFormat.OpenXml.StringValue>()
                        {
                            InnerText = string.Format("E2:E{0}", row)
                        },
                        Formula1 = new Formula1("CHANGESET_TYPES")
                    };
                    dataValidations1.Append(changesetTypeDataValidation);

                    var projectTypeDataValidation = new DataValidation()
                    {
                        Type = DataValidationValues.List,
                        AllowBlank = true,
                        ShowInputMessage = true,
                        ShowErrorMessage = true,
                        SequenceOfReferences = new DocumentFormat.OpenXml.ListValue<DocumentFormat.OpenXml.StringValue>()
                        {
                            InnerText = string.Format("D2:D{0}", row)
                        },
                        Formula1 = new Formula1("PROJECT_TYPES")
                    };
                    dataValidations1.Append(projectTypeDataValidation);

                    excel.Execute(changeLogsSheetName, ws =>
                    {
                        if (ws.Elements<PageMargins>().Any())
                            ws.InsertBefore(dataValidations1, ws.Elements<PageMargins>().First());
                        else
                            ws.Append(dataValidations1);

                        //var validator = new OpenXmlValidator();
                        //var result = validator.Validate(excel.Document);
                        //if (result.Any())
                        //{ 
                        //}

                        ws.Save();
                    });
                }

                MessageBox.Show("Generated Success.");
            }
        }
        #endregion

        #region Other Events
        private void ByDateRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            StartChangesetNumberTextBox.Enabled = false;
            EndChangesetNumberTextBox.Enabled = false;
            StartDateTimePicker.Enabled = true;
            EndDateTimePicker.Enabled = true;
        }

        private void ByNumberRadioButton_CheckedChanged(object sender, EventArgs e)
        { 
            StartChangesetNumberTextBox.Enabled = true;
            EndChangesetNumberTextBox.Enabled = true;
            StartDateTimePicker.Enabled = false;
            EndDateTimePicker.Enabled = false;
        }

        private void StartChangesetNumberTextBox_DoubleClick(object sender, EventArgs e)
        {
            if (AuthenticatedTfsTeamProjectCollection == null || !AuthenticatedTfsTeamProjectCollection.HasAuthenticated)
            {
                MessageBox.Show("Please connect the team project collection at first.");
                return;
            }

            var selectChangesetForm = new SelectChangesetForm(AuthenticatedTfsTeamProjectCollection, SourceCodeFolderTextBox.Text);
            selectChangesetForm.ShowDialog();
            if (!selectChangesetForm.IsCancelled && selectChangesetForm.SelectedChangeset!=null)
            {
                StartChangesetNumberTextBox.Text = selectChangesetForm.SelectedChangeset.ChangesetId.ToString();
            }
        }

        private void EndChangesetNumberTextBox_DoubleClick(object sender, EventArgs e)
        {
            if (AuthenticatedTfsTeamProjectCollection == null || !AuthenticatedTfsTeamProjectCollection.HasAuthenticated)
            {
                MessageBox.Show("Please connect the team project collection at first.");
                return;
            }

            var selectChangesetForm = new SelectChangesetForm(AuthenticatedTfsTeamProjectCollection, SourceCodeFolderTextBox.Text);
            selectChangesetForm.ShowDialog();
            if (!selectChangesetForm.IsCancelled && selectChangesetForm.SelectedChangeset != null)
            {
                EndChangesetNumberTextBox.Text = selectChangesetForm.SelectedChangeset.ChangesetId.ToString();
            }
        }
        #endregion

    }
}
